<?php 
/**
 * Session Class
 *
 */
class Session {
    const EXPIRE_TIME_ONE_YEAR = 31536000;
    private $sess_expiration = 7200;
    private $sess_match_ip = FALSE;
    private $sess_cookie_name = 'qsession';
    private $cookie_prefix = '';
    private $cookie_path = '/';
    private $cookie_domain = '';
    private $sess_time_to_update = 300;
    private $encryption_key = 'qtoken1026';
    private $userdata = array();
    private $now;
    
    /**
     * Session Constructor
     *
     */
    function Session($params = array()) {
        $this->now = time();
        
        // Set the session length. If the session expiration is
        // set to zero we'll set the expiration one years from now.
        if ($this->sess_expiration == 0) {
            $this->sess_expiration = EXPIRE_TIME_ONE_YEAR;
        }
        
        // Set the cookie name
        $this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name;
        
        // Run the Session routine. If a session doesn't exist we'll
        // create a new one.  If it does, we'll update it.
        if (!$this->sess_read()) {
            $this->sess_create();
        } else {
            $this->sess_update();
        }
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Fetch the current session data if it exists
     *
     * @access	public
     * @return	bool
     */
    function sess_read() {
        // Fetch the cookie
        $session = $_COOKIE[$this->sess_cookie_name];
        // No cookie?  Goodbye cruel world!...
        if ($session === FALSE) {
        	Logger::debug('session', 'A session cookie was not found.');
            return FALSE;
        }
		/*
        // encryption was not used, so we need to check the md5 hash
        $hash = substr($session, strlen($session) - 32); // get last 32 chars
        $session = substr($session, 0, strlen($session) - 32);
        
		
        // Does the md5 hash match?  This is to prevent manipulation of session data in userspace
        if ($hash !== md5($session.$this->encryption_key)) {
        	Logger::debug('session', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.');
            $this->sess_destroy();
            return FALSE;
        }*/
        // Unserialize the session array
        $session = $this->_unserialize($session);
		
        // Is the session data we unserialized an array with the correct format?
        if (!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['last_activity'])) {
            $this->sess_destroy();
            return FALSE;
        }
        // Is the session current?
        if (($session['last_activity'] + $this->sess_expiration) < $this->now) {
            $this->sess_destroy();
            return FALSE;
        }
        
        // Does the IP Match?
        if ($this->sess_match_ip == TRUE AND $session['ip_address'] != getenv('REMOTE_ADDR')) {
            $this->sess_destroy();
            return FALSE;
        }
        
        // Session is valid!
        $this->userdata = $session;
        unset($session);
        
        return TRUE;
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Write the session data
     *
     * @access	public
     * @return	void
     */
    function sess_write() {
        // set the custom userdata, the session data we will set in a second
        $custom_userdata = $this->userdata;
        $cookie_userdata = array();
        
        // Before continuing, we need to determine if there is any custom data to deal with.
        // Let's determine this by removing the default indexes to see if there's anything left in the array
        // and set the session data while we're at it
        foreach (array('session_id', 'ip_address', 'last_activity') as $val) {
            unset($custom_userdata[$val]);
            $cookie_userdata[$val] = $this->userdata[$val];
        }

        // Did we find any custom data?  If not, we turn the empty array into a string
        // since there's no reason to serialize and store an empty array in the DB
        if (count($custom_userdata) === 0) {
            $custom_userdata = '';
        } else {
        	/*
            // Serialize the custom data array so we can store it
            $custom_userdata = $this->_serialize($custom_userdata);
            */
			foreach ($custom_userdata as $key => $val) {
				$cookie_userdata[$key] = $val;
			}
        }
        // Write the cookie.  Notice that we manually pass the cookie data array to the
        // _set_cookie() function. Normally that function will store $this->userdata, but
        // in this case that array contains custom data, which we do not want in the cookie.
        $this->_set_cookie($cookie_userdata);
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Create a new session
     *
     * @access	public
     * @return	void
     */
    function sess_create() {
        $sessid = '';
        while (strlen($sessid) < 32) {
            $sessid .= mt_rand(0, mt_getrandmax());
        }
        
        //TODO
        // To make the session ID even more secure we'll combine it with the user's IP
        $sessid .= getenv('REMOTE_ADDR');
        
        $this->userdata = array('session_id'=>md5(uniqid($sessid, TRUE)), 'ip_address'=>getenv('REMOTE_ADDR'), 'last_activity'=>$this->now);

        // Write the cookie
        $this->_set_cookie();
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Update an existing session
     *
     * @access	public
     * @return	void
     */
    function sess_update() {
        // We only update the session every five minutes by default
        if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now) {
            return;
        }
       
        // Save the old session id so we know which record to
        // update in the database if we need it
        $old_sessid = $this->userdata['session_id'];
        $new_sessid = '';
        while (strlen($new_sessid) < 32) {
            $new_sessid .= mt_rand(0, mt_getrandmax());
        }
        
		//TODO change ip_get()
        // To make the session ID even more secure we'll combine it with the user's IP
        $new_sessid .= getenv('REMOTE_ADDR');
        
        // Turn it into a hash
        $new_sessid = md5(uniqid($new_sessid, TRUE));
        
        // Update the session data in the session data array
        $this->userdata['session_id'] = $new_sessid;
        $this->userdata['last_activity'] = $this->now;
        
        // _set_cookie() will handle this for us if we aren't using database sessions
        // by pushing all userdata to the cookie.
        $cookie_data = NULL;
        
        // Write the cookie
        $this->_set_cookie($cookie_data);
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Destroy the current session
     *
     * @access	public
     * @return	void
     */
    function sess_destroy() {  
        // Kill the cookie
        setcookie($this->sess_cookie_name, addslashes(serialize(array())), ($this->now - 31500000), $this->cookie_path, $this->cookie_domain, 0);
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Fetch a specific item from the session array
     *
     * @access	public
     * @param	string
     * @return	string
     */
    function get_userdata_by_key($item) {
        return (!isset($this->userdata[$item])) ? FALSE : $this->userdata[$item];
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Fetch all session data
     *
     * @access	public
     * @return	mixed
     */
    function get_all_userdata() {
        return (!isset($this->userdata)) ? FALSE : $this->userdata;
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Add or change data in the "userdata" array
     *
     * @access	public
     * @param	mixed
     * @param	string
     * @return	void
     */
    function set_userdata($newdata = array(), $newval = '') {
        if (is_string($newdata)) {
            $newdata = array($newdata=>$newval);
        }
        
        if (count($newdata) > 0) {
            foreach ($newdata as $key=>$val) {
                $this->userdata[$key] = $val;
            }
        }

        $this->sess_write();
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Delete a session variable from the "userdata" array
     *
     * @access	array
     * @return	void
     */
    function unset_userdata($newdata = array()) {
        if (is_string($newdata)) {
            $newdata = array($newdata=>'');
        }
        
        if (count($newdata) > 0) {
            foreach ($newdata as $key=>$val) {
                unset($this->userdata[$key]);
            }
        }
        
        $this->sess_write();
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Write the session cookie
     *
     * @access	public
     * @return	void
     */
    function _set_cookie($cookie_data = NULL) {
        if (is_null($cookie_data)) {
            $cookie_data = $this->userdata;
        }
        
        // Serialize the userdata for the cookie
        $cookie_data = $this->_serialize($cookie_data);
        // if encryption is not used, we provide an md5 hash to prevent userside tampering
        //$cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);
		
        // Set the cookie
        setcookie($this->sess_cookie_name, $cookie_data, $this->sess_expiration + time(), $this->cookie_path, $this->cookie_domain, 0);
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Serialize an array
     *
     * This function first converts any slashes found in the array to a temporary
     * marker, so when it gets unserialized the slashes will be preserved
     *
     * @access	private
     * @param	array
     * @return	string
     */
    function _serialize($data) {
        if (is_array($data)) {
            foreach ($data as $key=>$val) {
                $data[$key] = str_replace('\\', '{{slash}}', $val);
            }
        } else {
            $data = str_replace('\\', '{{slash}}', $data);
        }
        
        return serialize($data);
    }
    // --------------------------------------------------------------------
    
    /**
     * Unserialize
     *
     * This function unserializes a data string, then converts any
     * temporary slash markers back to actual slashes
     *
     * @access	private
     * @param	array
     * @return	string
     */
    function _unserialize($data) {
        $data = unserialize(stripslashes($data));
        if (is_array($data)) {
            foreach ($data as $key=>$val) {
                $data[$key] = str_replace('{{slash}}', '\\', $val);
            }
            
            return $data;
        }
        
        return str_replace('{{slash}}', '\\', $data);
    }
}